{ ----------------------------------------------------------------------------
  Amstrad Notepad emulator
  Copyright (c) Michael Nixon (Zipplet) 2011.
  http://www.zipplet.co.uk/
  Licensed under the MIT license, please see mit_license.txt in the
  documentation directory.

  Notepad emulation core
  ---------------------------------------------------------------------------- }
unit notepad;

interface

uses classes, globals, notepadstate, beeper, cpuz80, keyboard, lcd, sysutils;

type
  eIOWriteHandler = procedure(address: byte; value: byte) of object;
  eIOReadHandler = function(address: byte): byte of object;

  tnotepad = class(tobject)
    private
    protected
    public
      { ------------------------------------------------------------------------
        Emulation objects
        ------------------------------------------------------------------------ }
      hBeeper: tbeeper;
      hCPU: tcpuz80;
      hKeyboard: tkeyboard;
      hLCD: tlcd;
      hState: tnotepadstate;
      ioReadHandler: array[$00..$FF] of eIOReadHandler;
      ioWriteHandler: array[$00..$FF] of eIOWriteHandler;

      constructor Create(cSystemType: eSystem; cRamSize: longint; cRomSize: longint);
      destructor Destroy; override;

      procedure hardReset;
      procedure setupIOHandlers;
      procedure ioWrite(address: byte; value: byte);
      function ioRead(address: byte): byte;

      { IO handlers }
      function dummyIOReadHandler(address: byte): byte;
      procedure dummyIOWriteHandler(address: byte; value: byte);
      function handleIORead_mmc(address: byte): byte;
      procedure handleIOWrite_mmc(address: byte; value: byte);
  end;

implementation

{ ----------------------------------------------------------------------------
  Write to an IO port - used by some support functions to make the code more
  readable. Not called by the main emulation loop.
  ---------------------------------------------------------------------------- }
procedure tnotepad.ioWrite(address: byte; value: byte);
begin
  self.ioWriteHandler[address](address, value);
end;

{ ----------------------------------------------------------------------------
  Read from an IO port - used by some support functions to make the code more
  readable. Not called by the main emulation loop.
  ---------------------------------------------------------------------------- }
function tnotepad.ioRead(address: byte): byte;
begin
  result := self.ioReadHandler[address](address);
end;

{ ----------------------------------------------------------------------------
  Assigns IO handlers to the IO ports that are emulated
  ---------------------------------------------------------------------------- }
procedure tnotepad.setupIOHandlers;
var
  port: byte;
begin
  { All ports initially get the dummy handler }
  for port := $00 to $FF do begin
    self.ioReadHandler[port] := self.dummyIOReadHandler;
    self.ioWriteHandler[port] := self.dummyIOWriteHandler;
  end;

  { io_mmc }
  for port := io_mmc to (io_mmc + io_mmc_range) do begin
    self.ioReadHandler[port] := self.handleIORead_mmc;
    self.ioWriteHandler[port] := self.handleIOWrite_mmc;
  end;
end;

{ ----------------------------------------------------------------------------
  Dummy IO read handler - returns zero
  ---------------------------------------------------------------------------- }
function tnotepad.dummyIOReadHandler(address: byte): byte;
begin
  result := $00;
  { Useful to log this in debug builds, as it might show up bugs }
  {$ifdef debug}
  writeln('Dummy IO read: $' + inttohex(address, 2));
  {$endif}
end;

{ ----------------------------------------------------------------------------
  Dummy IO write handler - does nothing
  ---------------------------------------------------------------------------- }
procedure tnotepad.dummyIOWriteHandler(address: byte; value: byte);
begin
  { Useful to log this in debug builds, as it might show up bugs }
  {$ifdef debug}
  writeln('Dummy IO write: $' + inttohex(address, 2) + ' = $' + inttohex(value, 2));
  {$endif}
end;

{ ----------------------------------------------------------------------------
  Handle a read from a memory management control register
  ---------------------------------------------------------------------------- }
function tnotepad.handleIORead_mmc(address: byte): byte;
var
  idx: byte;
begin
  { Get the value }
  idx := address - io_mmc;
  result := self.hState.mem_ctrl[idx];
end;

{ ----------------------------------------------------------------------------
  Handle a write to a memory management control register
  ---------------------------------------------------------------------------- }
procedure tnotepad.handleIOWrite_mmc(address: byte; value: byte);
var
  idx: byte;
begin
  { Store the value }
  idx := address - io_mmc;
  self.hState.mem_ctrl[idx] := value;
end;

{ ----------------------------------------------------------------------------
  Reset the system state to the initial power on state
  ---------------------------------------------------------------------------- }
procedure tnotepad.hardReset;
var
  port: byte;
begin
  self.hState.zeroSystemRAM;

  { These defaults come from nciospec.txt by Cliff Lawson }
  self.hState.lcd_startaddr := $00;
  for port := io_mmc to (io_mmc + io_mmc_range) do begin
    self.ioWrite(port, $00);
  end;
end;

{ ----------------------------------------------------------------------------
  tnotepad constructor
  ---------------------------------------------------------------------------- }
constructor tnotepad.Create(cSystemType: eSystem; cRamSize: longint; cRomSize: longint);
begin
  inherited Create;

  self.hState := tnotepadstate.Create(cSystemType, cRamSize, cRomSize);

  self.hBeeper := tbeeper.Create(self.hState);
  self.hCPU := tcpuz80.Create(self.hState);
  self.hKeyboard := tkeyboard.Create(self.hState);
  self.hLCD := tlcd.Create(self.hState);

  self.setupIOHandlers;
end;

{ ----------------------------------------------------------------------------
  tnotepad destructor
  ---------------------------------------------------------------------------- }
destructor tnotepad.Destroy;
begin
  { Must destroy all subcomponents before destroying the state object! }
  freeandnil(self.hBeeper);
  freeandnil(self.hCPU);
  freeandnil(self.hKeyboard);
  freeandnil(self.hLCD);

  freeandnil(self.hState);

  inherited Destroy;
end;

end.
